package com.my.okhttps;

import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ejlchina.okhttps.*;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.my.core.util.spring.SpringUtilStatic;
import com.my.okhttps.utils.EsbUtil;
import com.my.okhttps.utils.security.SecurityUtil;
import lombok.extern.slf4j.Slf4j;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import javax.net.ssl.*;
import java.net.SocketTimeoutException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * resources中文件配置的 不能使用依赖注入 当然value注解也无效
 * @author timo 2020/11/25
 */
@Slf4j
@Component
@Configuration
public class OkHttpsConfig implements Config {

    public static final String GRID_LOGIN_URL = "/jeecg-system/sys/mLogin";
    public static String GRID_BASE_URL = "";
    /**
     * token缓存
     */
    private static Cache<String, Object> caffeineCache;
    /**
     * 熔断缓存
     */
    private static Cache<String, Object> fusingCache;

    /**
     * token缓存
     */
    private static Cache<String, Object> healthCaffeineCache;
    public Cache<String, Object> getHealthCaffeineCache() {
        if (healthCaffeineCache == null) {
            healthCaffeineCache = Caffeine.newBuilder()
                    //默认失效为2小时，这里设置好1个半小时
                    .expireAfterWrite(90, TimeUnit.MINUTES)
                    .initialCapacity(100)
                    .maximumSize(1000)
                    .softValues()
                    .build();
        }
        return healthCaffeineCache;
    }
    @Autowired
    private Cache<String, Object> getCaffeineCache(){
        return Caffeine.newBuilder()
                // 设置最后一次写入或访问后经过固定时间过期
                .expireAfterWrite(30, TimeUnit.MINUTES)
                // 初始的缓存空间大小
                .initialCapacity(100)
                // 缓存的最大条数
                .maximumSize(1000)
                // 软引用：如果一个对象只具有软引用，则内存空间足够，垃圾回收器就不会回收它；如果内存空间不足了，就会回收这些对象的内存。
                .softValues()
                .build();
    }
    @Autowired
    public Cache<String, Object> getFusingCache(){
        if (fusingCache == null) {
            fusingCache = Caffeine.newBuilder()
                    .expireAfterWrite(30, TimeUnit.SECONDS)
                    .initialCapacity(100)
                    .maximumSize(1000)
                    .softValues()
                    .build();
        }
        return fusingCache;
    }

//    public RedisTemplate<String, Object> redisTemplate;

    @Override
    public void with(HTTP.Builder builder) {
//        ApplicationContext applicationContext = SpringUtil.getApplicationContext();
//        redisTemplate = (RedisTemplate<String, Object>) applicationContext.getBean("redisTemplate");

        int environ = SpringUtilStatic.environ;
        switch (environ){
            case 0:
                builder.baseUrl("http://192.168.30.16:9411");
                break;
            case 1:
                builder.baseUrl("http://192.168.30.16:9411");
                break;
            case 2:
                builder.baseUrl("http://192.168.30.16:10011");
                break;
            case 3:
                builder.baseUrl("http://192.168.4.25:8884");
                break;
            default:
                // 在这里对 HTTP.Builder 做一些自定义的配置
                builder.baseUrl("http://192.168.30.16:10011");
        }
        GRID_BASE_URL = builder.baseUrl();

        builder.addPreprocessor((Preprocessor.PreChain chain) -> {
            HttpTask<?> task = chain.getTask();//获得当前的HTTP任务
            String url = task.getUrl();
            log.info("okhttps___url___"+url);
            if(getFusingCache()!=null){
                Cache<String, Object> fusingCache = getFusingCache();
                Object ifPresent = fusingCache.getIfPresent(url);
                if(ifPresent!=null && (Integer) ifPresent==1){
                    task.cancel();
                    //熔断3分钟
                    log.info("okhttps___url___"+url+"___熔断3分钟");
                    chain.proceed();
                    return;
                }
            }
//            task.addHeader(message.getHeaderKey(),"application/json;charset=UTF-8");
            //标签判断直接indexof判断的，尽量注意这个问题，tag需要完全不一致
            boolean shebaoTagged2 = task.isTagged("SHE_BAO2");
            if(shebaoTagged2){
                //流水号
                String request_id = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 20);
                String timestamp = String.valueOf(System.currentTimeMillis());
                task.addUrlPara("access_key", "Tftb");
                task.addUrlPara("format", OkHttps.JSON);
                task.addUrlPara("request_id",request_id);
                task.addUrlPara("timestamp", timestamp);
                task.addUrlPara("version", "1.0");
                Map<String, String> urlParas = task.getUrlParas();
                urlParas.put("mock", "");

                String source = EsbUtil.generateSignSource(urlParas);
                String sign = null;
                try {
                    Map<String, String> headers = task.getHeaders();
                    String publicKeyStr = headers.get("publicKeyStr");
                    headers.remove("publicKeyStr");
//                    headers.remove(message.getHeaderKey());
                    sign = EsbUtil.signKey(source, publicKeyStr);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                sign = sign.replaceAll("[+]", "%2B");
                task.addUrlPara("sign", sign);
            }
            boolean shebaoTagged = task.isTagged("SHEBAO");
            if(shebaoTagged){
                String timestamp = String.valueOf(System.currentTimeMillis());
                task.addUrlPara("access_key", "lzszpt");
                task.addUrlPara("timestamp", timestamp);
                task.addUrlPara("version", "1.0");
                Map<String, String> urlParas = task.getUrlParas();
                urlParas.put("mock","");
                String source = EsbUtil.generateSignSource(urlParas);
                String sign = null;
                try {
                    Map<String, String> headers = task.getHeaders();
                    String publicKeyStr = headers.get("publicKeyStr");
                    headers.remove("publicKeyStr");
                    sign = EsbUtil.signKey(source,publicKeyStr);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                sign = sign.replaceAll("[+]", "%2B");
                task.addUrlPara("sign", sign);
            }

            boolean yibaoTagged = task.isTagged("YIBAO");
            if(yibaoTagged){
                Map<String, String> urlParas = task.getUrlParas();
                String apiNum = urlParas.get("apiNum");
                urlParas.remove("apiNum");

                String serviceCode = urlParas.get("serviceCode");
                String platId = urlParas.get("platId");
                String publicKeyStr = urlParas.get("publicKeyStr");
                urlParas.remove("serviceCode");
                urlParas.remove("platId");
                urlParas.remove("publicKeyStr");

                task.addUrlPara("serviceCode", serviceCode);
                task.addUrlPara("platId",platId);

                Map<String,String> secMap = SecurityUtil.encrypt(urlParas,publicKeyStr);
                task.addUrlPara("paramIn",secMap.get("paramIn"));
                //aes加密信息参数名为paramAes
                task.addUrlPara("paramAes",secMap.get("paramAes"));
                task.addUrlPara("apiNum", apiNum);
            }
            chain.proceed();
        });

        String loginPa = "{\"captcha\": \"\",\"checkKey\": \"\",\"password\": \"123456\",\"username\": \"admin\"}";
        HttpHeaderMessage wanggeLogin = new HttpHeaderMessage(loginPa,null,null,
                "wangge-https-X-Access-Token",false,GRID_LOGIN_URL,"0");
        HttpHeaderMessage healthLogin = new HttpHeaderMessage(null,null,null,
                "luzhou-https-healthToken",false,null,"1");

        builder.addSerialPreprocessor((Preprocessor.PreChain chain) -> {
            HttpTask<?> task = chain.getTask();

            boolean wanggeTagged = task.isTagged("wangge");
            if(wanggeTagged){
                //多个登录地址单独处理
                log.info("okhttps___token__url___"+GRID_LOGIN_URL);
                if(getFusingCache()!=null){
                    Cache<String, Object> fusingCache = getFusingCache();
                    Object ifPresent = fusingCache.getIfPresent(GRID_LOGIN_URL);
                    if(ifPresent!=null && (Integer) ifPresent==1){
                        task.cancel();
                        //熔断3分钟
                        log.info("okhttps___token__url___"+GRID_LOGIN_URL+"___熔断3分钟");
                        chain.proceed();
                        return;
                    }
                }
                try {
                    if (caffeineCache == null) {
                        caffeineCache = getCaffeineCache();
                    }
                    task.addHeader("X-Access-Token",requestTokenAndRefreshIfExpired(wanggeLogin,caffeineCache));

//                    task.addHeader("X-Access-Token",requestTokenAndRefreshIfExpired(wanggeLogin,30));
                } catch (JsonProcessingException e) {
                    log.info("okhttps___token__url___"+GRID_LOGIN_URL+"_socketTimeoutException__响应解析异常");
                    task.cancel();
                    chain.proceed();
                    return;
                } catch (SocketTimeoutException socketTimeoutException){
                    log.info("okhttps___token__url___"+GRID_LOGIN_URL+"_socketTimeoutException__链接超时");
                    task.cancel();
                    chain.proceed();
                    return;
                }
            }

            boolean healthTar = task.isTagged("health_tar");
            if(healthTar){
                try {
                    Map<String, String> urlParas = task.getUrlParas();
                    String loginUrl = urlParas.get("loginUrl");
                    String loginPaMap = urlParas.get("loginPaMap");
                    urlParas.remove("loginUrl");
                    urlParas.remove("loginPaMap");
                    healthLogin.setLoginUrl(loginUrl);
                    healthLogin.setRequestBody(loginPaMap);
                    task.addUrlPara("token",requestTokenAndRefreshIfExpired(healthLogin,getHealthCaffeineCache()));

//                    task.addUrlPara("token",requestTokenAndRefreshIfExpired(healthLogin,90));
                } catch (JsonProcessingException e) {
                    log.info("okhttps___token__url___"+GRID_LOGIN_URL+"_socketTimeoutException__响应解析异常");
                    task.cancel();
                    chain.proceed();
                    return;
                } catch (SocketTimeoutException socketTimeoutException) {
                    log.info("okhttps___token__url___"+GRID_LOGIN_URL+"_socketTimeoutException__链接超时");
                    task.cancel();
                    chain.proceed();
                    return;
                }
            }
            chain.proceed(); // 调用此方法前，不会有其它任务进入该处理器
        });



        builder.config((OkHttpClient.Builder builders) -> {
            // 配置连接池 最小10个连接（不配置默认为 5）
            builders.connectionPool(new ConnectionPool(10, 5, TimeUnit.MINUTES));
            // 配置连接超时时间（默认10秒）
            builders.connectTimeout(5, TimeUnit.SECONDS);
            // 配置拦截器
            HttpLoggingInterceptor.Logger logging = (messages) -> {
                log.info(messages);
            };
            HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(logging);
            loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
            builders.addInterceptor(loggingInterceptor);


            //如果服务器的 SSL 证书不是在权威机构购买而是自己生成的（不推荐这种做法），则需要配置sslSocketFactory和hostnameVerifier即可：
            X509TrustManager myTrustManager = new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            };
            HostnameVerifier myHostnameVerifier = new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            };
            SSLContext sslCtx = null;
            try {
                sslCtx = SSLContext.getInstance("TLS");
                sslCtx.init(null, new TrustManager[] { myTrustManager }, new SecureRandom());
            } catch (NoSuchAlgorithmException | KeyManagementException e) {
                e.printStackTrace();
            }
            SSLSocketFactory mySSLSocketFactory = sslCtx.getSocketFactory();

            builders.sslSocketFactory(mySSLSocketFactory, myTrustManager);
            builders.hostnameVerifier(myHostnameVerifier);
        });

        builder.bodyType(OkHttps.JSON);

//        builder.config(b -> {
//            b.sslSocketFactory(mySSLSocketFactory, myTrustManager);
//            b.hostnameVerifier(myHostnameVerifier);
//        });
    }

    /**
     * 根据配置的缓存失效时间自动获取/刷新token
     *
     * @param message   http消息
     * @throws JsonProcessingException
     */
    public String requestTokenAndRefreshIfExpired(HttpHeaderMessage message,
//                                                  int time
//                                                  不使用内存缓存
                                                  Cache<String, Object> cache
    ) throws JsonProcessingException, SocketTimeoutException {
        // 根据断言判断token 是否过期
        if (message.isAssertion() == false){
//            return getToken(message,time);
            return getToken(message,cache);
        }
        // 将token 从本地缓存中取出来
        Object cacheToken = cache.getIfPresent(message.getHeaderKey());
//        Object cacheToken = redisTemplate.boundValueOps(message.getHeaderKey()).get();
        log.info("http message.getHeaderKey() -> " + message.getHeaderKey() + " -> " + cacheToken);
        return getToken(message, cacheToken,cache);
//        return getToken(message, cacheToken,time);
    }
    /**
     * 根据设置的缓存失效时间,自动刷新token
     *
     * @param message   http 消息体
     * @param cacheToken    从缓存中取出的token
     * @return
     * @throws JsonProcessingException
     */
    private String getToken(HttpHeaderMessage message, Object cacheToken
                            ,Cache<String, Object> cache
//                            ,int time
    ) throws JsonProcessingException, SocketTimeoutException {
        // 判断token是否为空，为空重新获取，否则直接插入
        if ("".equals(cacheToken) || cacheToken == null){
            return getToken(message,cache);
//            return getToken(message,time);
        }else {
            log.info("从缓存中取出的token -> " + cacheToken);
            return cacheToken.toString();
//            return cache.getIfPresent(message.getHeaderKey()).toString();
        }
    }
    /**
     * 根据接口响应断言自动刷新token(类似于一个兜底的方法,在不设置缓存实效的情况下)
     *
     * @param message http 消息体
     * @return
     * @throws JsonProcessingException
     */
    private String getToken(HttpHeaderMessage message
//            ,int time
            ,Cache<String, Object> cache
    ) throws JsonProcessingException, SocketTimeoutException {
       String token = "";
       switch (message.getType()){
           case "0":
               // 发送post 请求获取token
               HttpResult httpResult = OkHttps.sync(message.getLoginUrl())
                       .skipSerialPreproc()    // 跳过串行预处理器
                       .setBodyPara(message.getRequestBody())
                       .nothrow()
                       .post();

               switch (httpResult.getState()) {
                   case TIMEOUT:
                       // 请求超时
                       Cache<String, Object> fusingCache = getFusingCache();
                       fusingCache.put(GRID_LOGIN_URL, 1);
                       log.info("请求超时！进行熔断处理");
                       throw new SocketTimeoutException();
                   case RESPONSED:
                       log.info("okhttps请求已正常响应");
                       break;
                   case CANCELED:
                       log.info("okhttps请求已被取消");
                       break;
                   case NETWORK_ERROR:
                       log.info("okhttps网络错误，说明用户没网了");
                       break;
                   case EXCEPTION:
                       log.info("okhttps其它异常");
                       break;
                   default:
                       log.info("okhttps default");
//                log.error(error!=null?error.getMessage():"okhttps熔断");
//                throw new Exception(error!=null?error.getMessage():"okhttps熔断");
               }
               String resString = httpResult.getBody().toString();
//               Result result = JSON.parseObject(resString, Result.class);
//               // 从返回的json 中获取token
//               log.info("http loginBody -> "+result.toString());
//               JSONObject jsonObject = (JSONObject)result.getResult();
//               token = jsonObject.getString("token");
               log.info("http token -> "+token);
               break;
           case "1":
               String requestBody = message.getRequestBody();
               Map map = JSONUtil.toBean(requestBody, Map.class);
               HttpResult httpResult2 = OkHttps.sync(message.getLoginUrl())
                       .addUrlPara(map)
                       .skipSerialPreproc()
                       .get();
               HttpResult.Body body = httpResult2.getBody();
               JSONObject jsonObject2 = JSON.parseObject(String.valueOf(body));
               String code = jsonObject2.getString("code");
               if("0".equals(code)) {
                   token = jsonObject2.getString("access_token");
               }else {
                   String msg = jsonObject2.getString("msg");
                   log.error("健康服务获取token失败："+msg);
               }
               break;
           default:
       }
        message.setAssertion(true);
        // 判断是否需要在token 前增加字符串，然后存入到缓存中
        if (message.getToken() == null || StringUtils.isBlank(message.getToken())) {
            cache.put(message.getHeaderKey(), token);
//            redisTemplate.boundValueOps(message.getHeaderKey()).set(token,time, TimeUnit.MINUTES);
            return token;
        } else {
            cache.put(message.getHeaderKey(), message.getToken() + token);
//            redisTemplate.boundValueOps(message.getHeaderKey()).set(message.getToken() + token,time, TimeUnit.MINUTES);
            return message.getToken() + token;
        }

    }

//    @Autowired
//    private OkHttpsConfig okHttpsConfig;
//    public void resultState(HttpResult result,String url) throws Exception {
//        Cache<String, Object> fusingCache = okHttpsConfig.getFusingCache();
//        boolean http = url.startsWith("http");
//        if(!http){
//            url = OkHttpsConfig.GRID_BASE_URL+url;
//        }
//        // 还可以获得具体的异常信息
//        IOException error = result.getError();
//        // 判断执行状态
//        switch (result.getState()) {
//            case TIMEOUT:
//                // 请求超时
//                fusingCache.put(url, 1);
//                log.info("请求超时！进行熔断处理");
//                throw new SocketTimeoutException();
//            case RESPONSED:
//                log.info("okhttps请求已正常响应");
//                break;
//            case CANCELED:
//                log.info("okhttps请求已被取消");
//                break;
//            case NETWORK_ERROR:
//                log.info("okhttps网络错误，说明用户没网了");
//                break;
//            case EXCEPTION:
//                log.info("okhttps其它异常");
//                break;
//            default:
//                log.info("okhttps default");
////                log.error(error!=null?error.getMessage():"okhttps熔断");
////                throw new Exception(error!=null?error.getMessage():"okhttps熔断");
//        }
//    }
//    public Result resultConversion(HttpResult result,String url) throws Exception {
//        resultState(result,url);
//        HttpResult.Body body = result.getBody();
//        if(body==null){
//            //请求被取消 熔断
//            return Result.fail("请求被取消-熔断(接口连接超时),3分钟之后重试");
//        }
//        String bodyStr = body.toString();
//        try {
//            return JSON.parseObject(bodyStr, Result.class);
//        }catch (com.alibaba.fastjson.JSONException e){
//            log.error("okhttp响应值转化异常:"+bodyStr);
//            return Result.fail("okhttp响应值转化异常:"+bodyStr);
//        }
//    }
}
